package rpc

import (
	"context"
	"errors"
	"log"
	"net"
	"reflect"
	"strconv"
	"time"

	"gitee.com/youkelike/rpc/message"
	"gitee.com/youkelike/rpc/message/compress"
	"gitee.com/youkelike/rpc/message/serialize"
	"gitee.com/youkelike/rpc/message/serialize/json"
)

type Server struct {
	services    map[string]reflectionStub
	serializers map[uint8]serialize.Serializer
	compressors map[uint8]compress.Compressor
}

func NewServer() *Server {
	res := &Server{
		services:    make(map[string]reflectionStub, 10),
		serializers: make(map[uint8]serialize.Serializer, 4),
		compressors: make(map[uint8]compress.Compressor, 4),
	}

	res.RegisterSerializer(&json.Serializer{})
	res.RegisterCompressor(compress.DoNothingCompressor{})
	return res
}

func (s *Server) RegisterSerializer(sl serialize.Serializer) {
	s.serializers[sl.Code()] = sl
}
func (s *Server) RegisterCompressor(compress compress.Compressor) {
	s.compressors[compress.Code()] = compress
}

func (s *Server) RegisterService(service Service) {
	s.services[service.Name()] = reflectionStub{
		// s:           service,
		value:       reflect.ValueOf(service),
		serializers: s.serializers,
		compressors: s.compressors,
	}
}

func (s *Server) Start(network, addr string) error {
	listener, err := net.Listen(network, addr)
	if err != nil {
		return err
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			return err
		}

		go func() {
			if er := s.handleConn(conn); er != nil {
				_ = conn.Close()
			}
		}()
	}
}

// 只负责连接上的数据读写，其它操作通过调用别的方法来完成
func (s *Server) handleConn(conn net.Conn) error {
	for {
		// 接收数据
		reqBs, err := ReadMsg(conn)
		if err != nil {
			return err
		}

		// 解读请求数据
		reqData := message.DecodeReq(reqBs)

		// 执行业务逻辑
		respData, err := s.Invoke(context.Background(), reqData)
		if err != nil {
			// 发生服务方法不存在、单次调用等情况
			log.Println(err.Error())
			continue
		}

		// 组装响应数据：8 字节的响应内容长度 + 响应内容
		respBs := message.EncodeResp(respData)

		// 发送数据
		_, err = conn.Write(respBs)
		if err != nil {
			return err
		}
	}
}

// 通过 Request 来构造 Response，
// 主要是 Response.Data、Response.Err 两个字段需要填充，通过调用对应的服务方法就能得到
func (s *Server) Invoke(ctx context.Context, req *message.Request) (*message.Response, error) {
	resp := &message.Response{
		RequestId:  req.RequestId,
		Version:    req.Version,
		Compresser: req.Compresser,
		Serializer: req.Serializer,
	}

	// 找服务
	service, ok := s.services[req.ServiceName]
	if !ok {
		return resp, errors.New("rpc调用的服务不存在")
	}

	// 处理超时
	cancel := func() {}
	if deadlineStr, ok := req.Meta["deadline"]; ok {
		if deadline, err := strconv.ParseInt(deadlineStr, 10, 64); err == nil {
			ctx, cancel = context.WithDeadline(ctx, time.UnixMilli(deadline))
		}
	}
	defer cancel()

	// 处理单次调用
	oneway, ok := req.Meta["one-way"]
	if ok && oneway == "true" {
		go func() {
			_, _ = service.invoke(ctx, req)
		}()
		return nil, errors.New("micro: 微服务收到 oneway 调用")
	}

	res, err := service.invoke(ctx, req)
	if err != nil {
		resp.Err = []byte(err.Error())
	}
	resp.Data = res
	// rpc 方法执行成功正常返回，第二个参数为 nil
	return resp, nil
}

type reflectionStub struct {
	// s           Service
	value       reflect.Value
	serializers map[uint8]serialize.Serializer
	compressors map[uint8]compress.Compressor
}

// 根据方法名，动态调用 struct 的方法
func (s *reflectionStub) invoke(ctx context.Context, req *message.Request) ([]byte, error) {
	// 找方法
	method := s.value.MethodByName(req.MethodName)

	// 构造方法的入参
	in := make([]reflect.Value, 2)
	in[0] = reflect.ValueOf(ctx)
	inArg := reflect.New(method.Type().In(1).Elem())

	c, ok := s.compressors[req.Compresser]
	if !ok {
		return nil, errors.New("micro: 不支持的压缩协议")
	}
	data, err := c.Decompress(req.Data)
	if err != nil {
		return nil, err
	}

	serializer, ok := s.serializers[req.Serializer]
	if !ok {
		return nil, errors.New("micro: 不支持的序列化协议")
	}
	err = serializer.Decode(data, inArg.Interface())
	if err != nil {
		return nil, err
	}

	in[1] = inArg

	// 调用方法
	results := method.Call(in)

	var e error
	var res []byte

	if !results[0].IsNil() {
		res, e = serializer.Encode(results[0].Interface())
		if e != nil {
			return nil, e
		}
		res, e = c.Compress(res)
		if e != nil {
			return nil, e
		}
	}
	if !results[1].IsNil() {
		e = results[1].Interface().(error)
		return res, e
	}
	return res, nil
}
